# encoding: utf-8
import json
import time
import traceback
from multiprocessing.dummy import Pool as ThreadPool

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

import com.util.assertUtil as assertUtil
from com.stock.mainStock.stockList import stock_list
from com.util.MongoUtil import MongoUtil
from com.util.httpRequest import HttpRequest
from com.util.loggingUtil import Logs
from com.util.pushUtil import pushDingDing

Log = Logs()

client = MongoUtil('k_line_item')
db = client.db
collection = client.col


def checkMongoNotNone(code, marketType, candleMode=1, state=1):
    result = collection.find({
        'stock_id': code,
        'type': {"$in": [6, 7, 8, 9, 10]},
        'market_type': marketType,
        'candle_mode': candleMode, 'state': state,
        "$or": [
            {'open_price': {'$in': [None, '', 0]}},
            {'close_price': {'$in': [None, '', 0]}},
            {'highest_price': {'$in': [None, '', 0]}},
            {'lowest_price': {'$in': [None, '', 0]}},
        ]})
    failNoneList = []
    for obj in result:
        date = str(obj['market_date'])
        if obj.__contains__('trade_amount'):
            trade_amount = obj['trade_amount']
        else:
            trade_amount = None
        if date >= '20220101' and trade_amount is not None:
            Log.error(f"为空错误{obj['stock_id']}-{date}---type:{obj['type']}")
            failNoneList.append(f"{date}-{obj['type']}")

    if failNoneList.__len__() > 0:
        failNoneMap.setdefault(code, failNoneList)
    else:
        Log.info(f"{code}-校验None通过")
    return result


def removeRealNoneData(http, code):
    # 数据库中为None的数据
    failNoneList: list = failNoneMap.get(code)
    if failNoneList: # failNoneList 不为空时
        for key in failNoneList:
            len = str(key).__len__()
            market_date = str(key)[:len - 2]
            httpData = http.get(market_date)
            if not httpData:  # 数据不存在
                failNoneList.remove(key)
        if failNoneList is not None and failNoneList.__len__() == 0:
            failNoneMap.pop(code, None)


def getXueQiuCookie():
    # # 启动浏览器
    chrome_options = Options()
    chrome_options.add_argument("headless")  # 无头模式
    chrome_options.add_argument('--no-sandbox')  # 参数是让Chrome在root权限下跑
    chrome_options.add_argument('–-disable-dev-shm-usage')
    chrome_options.add_argument('–-disable-gpu')
    chrome_options.add_argument('--ignore-certificate-errors')
    driver = webdriver.Chrome(options=chrome_options)
    # driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=chrome_options)

    url = 'https://xueqiu.com/S/' + 'SH600062'
    driver.get(url)
    time.sleep(2)

    cookies = driver.get_cookies();
    Cookie = ""
    for cookie in cookies:
        item = f'{cookie["name"]}={cookie["value"]};'
        Cookie = Cookie + item;
    driver.close()
    return Cookie;


XueQiuCookie = getXueQiuCookie()


def getDongFangCaiFuKline(code=str, kType=int, limit=int, market=1, candleMode=1):
    """
    获取东方财富的k线数据
    :param code: 股票代码
    :param kType:  传输wogoo - k线类型
    :param limit:  限制获取条数
    :param market: 传输wogoo - mongo的市场数据
    :return:
    """
    chaneData = {
        '6': '101',  # wogoo-mongo -> 东财
        '7': '102',
        '8': '103',
        '9': '104',
        '10': '106'
    }
    market = 1 if market == 1 else 0
    fqt = 1 if candleMode == 2 else 0  # 前复权
    type = chaneData[str(kType)]
    dcUrl = f"http://88.push2his.eastmoney.com/api/qt/stock/kline/get?" \
            f"ut=fa5fd1943c7b386f172d6893dbfba10b&secid={str(market)}.{code}" \
            f"&cb=jQuery112406633754314097817_1636789827368&fields1=f1%2Cf2%2Cf3%2Cf4%2Cf5%2Cf6" \
            f"&fields2=f51%2Cf52%2Cf53%2Cf54%2Cf55%2Cf56%2Cf57%2Cf58%2Cf59%2Cf60%2Cf61" \
            f"&klt={type}&fqt={str(fqt)}&end=20500101&lmt={str(limit)}&_=1636789827487"
    res = HttpRequest().http_request(method='GET', url=dcUrl)

    klines = []
    try:
        result = res[res.find('{'):res.__len__() - 2]
        jsonRes = json.loads(result)
        if result.__contains__('data') and result.__contains__('klines'):
            klines = jsonRes['data']['klines']
    except:
        Log.error(f"---无法解析result---:{result}")

    returnRes = dict()
    for i in klines:
        datas = str(i).split(',')
        date = str(datas[0]).replace('-', '')
        singleData = {
            # 开盘
            'open_price': float(datas[1]),
            # 收盘
            'close_price': float(datas[2]),
            # 最高价
            'highest_price': float(datas[3]),
            # 最低价
            'lowest_price': float(datas[4]),
            # 成交量
            'trade_amount': int(datas[5]) * 100,  # 股数
            # 成交额
            'trade_val': float(datas[6]),
            # 振幅
            'zhengfu': float(datas[7]),
            # 涨跌幅
            'change_pct': float(datas[8]),
            # 涨跌额
            'change_amount': float(datas[9]),
            # 换手率
            'exchange_ratio': float(datas[10])
        }
        returnRes.setdefault(date, singleData)
    return returnRes


def getMongoDbKline(code, kType, limit, marketType, candleMode=1, state=1):
    """
    获取mongoDb数据库里的k线数据
    :param code:  股票代码
    :param kType: k线类型
    :param limit: 限制条数
    :param marketType: 市场类型
    :param candleMode: 1: 不复权 ； 2 前复权
    :param state: 1: 有效 ； 2 失效
    :return:
    """

    dbResult = collection.find(
        {'stock_id': code, 'type': kType, 'market_type': marketType, 'candle_mode': candleMode, 'state': state}).limit(
        limit)

    return dbResult


def compareKline(dbResult, http, typeName):
    """
    比较分时数据
    :param dbResult:  mongodb数据
    :param httpA:  抓取的数据
    :param typeName: 比较的数据
    :return:
    """
    failDict = {}
    successNum = 0

    for obj in dbResult:  # 遍历数据
        # 日期
        market_date = obj['market_date']
        # 雪球数据
        httpData: dict = http.get(str(market_date))
        singleFailDict = {}

        if not httpData:  # 不存在数据直接略过
            continue
        # 最高价
        highest_price = obj['highest_price']
        http_highest_price = httpData.get('highest_price')
        if not assertUtil.compareFloat(highest_price, http_highest_price):
            singleFailDict.setdefault('highest_price', f"{highest_price}!={http_highest_price}")

        # 最低价
        lowest_price = obj['lowest_price']
        http_lowest_price = httpData['lowest_price']
        if not assertUtil.compareFloat(lowest_price, http_lowest_price):
            singleFailDict.setdefault('lowest_price', f"{lowest_price}!={http_lowest_price}")

        # 开盘价
        open_price = obj['open_price']
        http_open_price = httpData['open_price']
        if not assertUtil.compareFloat(open_price, http_open_price):
            singleFailDict.setdefault('open_price', f"{open_price}!={http_open_price}")

        # 收盘价
        close_price = obj['close_price']
        http_close_price = httpData['close_price']
        if not assertUtil.compareFloat(close_price, http_close_price):
            singleFailDict.setdefault('close_price', f"{close_price}!={http_close_price}")

        # 成交量
        trade_amount = float(obj['trade_amount']) / 100  # 手
        http_trade_amount = float(httpData['trade_amount']) / 100  # 手
        if not assertUtil.compareAmount(trade_amount, http_trade_amount, 2):
            singleFailDict.setdefault('trade_amount', f"{trade_amount}!={http_trade_amount}")

        # 成交额
        trade_val = float(obj['trade_val'])
        http_trade_val = httpData['trade_val']
        if not assertUtil.compareAmount(trade_val, http_trade_val, 2):
            singleFailDict.setdefault('trade_val', f"{trade_val}!={http_trade_val}")

        # # 涨跌额 单独处理
        # change_amount = 0 if not obj.__contains__('change_amount') else obj['change_amount']
        # assertUtil.assertFlaot(change_amount, httpData['change_amount'])

        # 涨跌幅 单独处理
        # change_pct = 0 if not obj.__contains__('change_pct') else float(obj['change_pct']) * 100
        # assertUtil.assertFlaot(change_pct, httpData['change_pct'])

        # 换手率
        # exchange_ratio = None if not obj.__contains__('exchange_ratio') else float(obj['exchange_ratio']) * 100
        # assertUtil.assertExchangeRadio(exchange_ratio, httpBData['exchange_ratio'],0.5)

        if singleFailDict.__len__() == 0:
            successNum = successNum + 1
        else:
            failDict.setdefault(market_date, singleFailDict)

    failNum = failDict.__len__()

    Log.info(f"code compare:[{typeName[2]}]总数:{(successNum + failNum)}，成功:{successNum}，失败:{failNum}")
    if failNum > 0 and failNum <= 10:
        failMap.setdefault(typeName[2], {"failNum": failNum, "failDict": failDict})
    elif failNum > 10:
        failMap.setdefault(typeName[2], {"failNum": failNum, "failDict": "too many and see log"})
        Log.info(f"code:{typeName[2]} errorData,[{failDict}]")
    else:
        successMap.setdefault(typeName[2], (successNum + failNum))


def getFutuKline():
    """
    默认前复权 ； 每次需更换stock_id/market_type/ type为k线类型
    {
        c: 212210        ： 收盘价
        h: 212520        ： 最高价 需/1000
        k: 1636992000    ： 时间戳
        l: 205110        ： 最低价
        lc: 204700       ： 昨收价
        o: 205890        ： 开盘价
        r: 0.00454       ： 换手率
        t: 3687421560.92 ： 交易额
        v: 17614237      ： 成交量
    }
    num = 6 不复权
    :return:
    """
    get_url = "https://www.futunn.com/quote-api/get-kline?stock_id=76798312520531&market_type=4&type=2"
    cookie_headers = {
        'Cookie': 'locale=zh-cn; quote-csrf=ziwJgPuQkFdpNH0jdUdK2alt25Q=; cipher_device_id=1636775326752299; device_id=1636775326752299; futunn_lang=zh-CN; _csrf=uLg2yAtD0q36bhsnxTuVAXZrIRMcU8Bf; FUTU_TOOL_STAT_UNIQUE_ID=16367753544582715; UM_distinctid=17d1769e106dc-037c592928c818-1c306851-13c680-17d1769e10717a1; CNZZDATA1256186977=1028868872-1636766510-https%253A%252F%252Fwww.futunn.com%252F%7C1636766510; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22ftv1eKF263IN34kcoftdfOfJ3cYQ9qLqJTMxHdNZ%2FONrqYzUL0xVDM9gqUPQug7fsILh%22%2C%22first_id%22%3A%22ftv1eKF263IN34kcoftdfOfJ3cYQ9qLqJTMxHdNZ%2FONrqYzUL0xVDM9gqUPQug7fsILh%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E4%BB%98%E8%B4%B9%E5%B9%BF%E5%91%8A%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC%22%2C%22%24latest_referrer%22%3A%22https%3A%2F%2Fwww.baidu.com%2Flink%22%2C%22%24latest_utm_source%22%3A%22alading_user%22%2C%22%24latest_utm_medium%22%3A%22website_growth%22%7D%2C%22%24device_id%22%3A%2217d176971c81453-0fb3de01d378df-1c306851-1296000-17d176971c9829%22%7D; Hm_lvt_f3ecfeb354419b501942b6f9caf8d0db=1636775326,1636976099; Hm_lpvt_f3ecfeb354419b501942b6f9caf8d0db=1637030122; tgw_l7_route=95db51d9431dfc9dd9b07b93baf6d779',
        'referer': 'https://www.futunn.com/stock/000858-SZ',
        'futu-x-csrf-token': 'QZeXVN2KdVV7/NjJC90hUg==-diff+6FMduVCXNZmeUo24cd9vZc='
    }

    resStr = HttpRequest.http_request(method='GET', url=get_url, headers=cookie_headers)
    resData = resStr[resStr.find('{'):resStr.__len__() - 1]
    jsonRes = json.loads(resData)
    list = []
    if resData.__contains__('data') and resData.__contains__('list'):
        list = jsonRes['data']['list']
    returnRes = dict()
    for obj in list:
        timeStamp = obj['k']
        timeArray = time.localtime(timeStamp)
        date = str(time.strftime("%Y%m%d", timeArray))
        singleData = {
            # 开盘
            'open_price': float(obj['o']) / 1000,
            # 收盘
            'close_price': float(obj['c']) / 1000,
            # 最高价
            'highest_price': float(obj['h']) / 1000,
            # 最低价
            'lowest_price': float(obj['l']) / 1000,
            # 成交量
            'trade_amount': float(obj['v']),
            # 成交额
            'trade_val': float(obj['t']),
            # 涨跌幅
            'change_pct': (float(obj['c']) - float(obj['o'])) / float(obj['lc']),
            # 涨跌额
            'change_amount': (float(obj['c']) - float(obj['lc'])) / 1000,
            # 换手率
            'exchange_ratio': float(obj['r'])
        }
        returnRes.setdefault(date, singleData)
    return returnRes


def getXueQiuKline(code, marketType, limit, type):
    """
    通过雪球获取前复权数据
    :param code:  股票
    :param marketType:市场类型
    :param limit: 获取条数
    :param type: 前复权是否（before/normal）
    :return:
    """
    # 是否是转债
    isKZZ = 1
    if code.startswith("11") or code.startswith("12"):
        isKZZ = 10

    marketMapping = {1: "SH", 2: "SZ", 18: "BJ"}
    marketStr = marketMapping.get(marketType)

    codeStr = marketStr + code
    msTime = int(time.time() * 1000).__str__()

    get_url = f"https://stock.xueqiu.com/v5/stock/chart/kline.json?" \
              f"symbol={codeStr}&begin={msTime}&period=day" \
              f"&type={type}&count=-{limit}&indicator=kline,pe,pb,ps,pcf,market_capital,agt,ggt,balance"
    cookie_headers = {
        'Cookie': XueQiuCookie,
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36'
    }
    resData = HttpRequest().http_request(method='GET', url=get_url, headers=cookie_headers)

    list = []
    if resData and resData.__contains__('data'):
        if resData['data'].__contains__('item'):
            list = resData['data']['item']

    if list.__len__() ==0 :
        Log.error(f'解析错误{get_url}'
                  f'-{resData}')

    # paramType = ['timestamp', 'volume', 'open', 'high', 'low', 'close', 'chg', 'percent', 'turnoverrate', 'amount']
    returnRes = {}
    for datas in list:
        timeStamp = datas[0]
        timeArray = time.localtime(float(timeStamp) / 1000)
        date = time.strftime("%Y%m%d", timeArray)

        singleData = {
            # 开盘
            'open_price': toFloat(datas[2]),
            # 收盘
            'close_price': toFloat(datas[5]),
            # 最高价
            'highest_price': toFloat(datas[3]),
            # 最低价
            'lowest_price': toFloat(datas[4]),
            # 成交量
            'trade_amount': toInt(datas[1] * isKZZ),  # 股 ， 转债需要此数据显示的为手需要*10
            # 成交额
            'trade_val': toInt(datas[9]),
            # 涨跌幅
            'change_pct': toFloat(datas[7]),
            # 涨跌额
            'change_amount': toFloat(datas[6]),
            # 换手率
            'exchange_ratio': toFloat(datas[8])
        }
        returnRes.setdefault(date, singleData)
    return returnRes


def getXueQiuKlineNormal(code, marketType, limit):
    return getXueQiuKline(code, marketType, limit, "normal")


def getXueQiuKlineBefore(code, marketType, limit):
    return getXueQiuKline(code, marketType, limit, "before")


def getXueQiuKlineAfter(code, marketType, limit):
    return getXueQiuKline(code, marketType, limit, "after")


def toFloat(num):
    if num == None:
        return 0.0
    else:
        return float(num)


def toInt(num):
    if num == None:
        return int(0)
    else:
        return int(num)


def checkNotNone(codeStr):
    datas = str(codeStr).split(".")
    code = str(datas[0])
    marketType = int(datas[1])
    # k线数据
    checkMongoNotNone(code, marketType)


def compareWithkLineBefore(codeStr):
    """
    前复权比较
    :param codeStr:
    :return:
    """
    compareWithkLine(codeStr, 2)


def compareWithkLineAfter(codeStr):
    """
    后复权比较
    :param codeStr:
    :return:
    """
    compareWithkLine(codeStr, 3)


def compareWithkLine(codeStr, candleMode):
    """
    :param codeStr:
    :param candleMode: 1 不复权  /  2 前复权 / 3 后复权
    :return:
    """
    datas = str(codeStr).split(".")
    code = str(datas[0])
    marketType = int(datas[1])
    # 北交所代码报错太多
    if code.startswith('159') or code.startswith('5') or code.startswith('8') or code.startswith('43'):
        return

    limit = 300
    kType = 6  # 6 // 日k  7 // 周k  8 // 月k  9 // 季k  10 // 年k

    try:
        dbResult = getMongoDbKline(code, kType, limit, marketType, candleMode)
        if candleMode == 2:
            http = getXueQiuKlineBefore(code, marketType, limit)
            # 校验空数据需要
            checkMongoNotNone(code, marketType)
            removeRealNoneData(http, code)
        elif candleMode == 3:
            http = getXueQiuKlineAfter(code, marketType, limit)
        compareKline(dbResult, http, ['xue qiu', 'dong cai', code])
    except KeyError as e:
        Log.error(f"-------code error [{codeStr}]\n{traceback.format_exc()}")
        failMap.setdefault(code, 'netWorkError')


# 结果
successMap = {}
failMap = {}
failNoneMap = {}


def changeNoneFailureToDate(failMap):
    """
    转换修复参数数据
    :param failMap:
    :return:
    """
    d2 = {}

    for key in failMap.keys():
        list = failMap.get(key)
        for i in list:
            _str = str(i)
            date = _str.split('-')

            type = date[1]
            dt = date[0]
            if not d2.__contains__(type):
                d2.setdefault(type, str(type + "," + dt))
            else:
                val = d2.get(type)
                if dt not in val:
                    d2.__setitem__(type, str(val + "," + dt))
    return d2


def run():
    # 对比前复权
    codeList = stock_list
    #codeList = codeList[0:100]

    # 前复权 即空数据校验
    pool = ThreadPool(5)
    pool.map(compareWithkLineBefore, codeList)
    pool.close()  # 关闭进程池，不再接受新的进程
    pool.join()  # 主进程阻塞等待子进程的退出

    totalsize = successMap.__len__() + failMap.__len__()
    resultContent = f"对比k线[前复权]总数:【{totalsize}】,校验成功:【{successMap.__len__()}】,校验失败【{failMap.__len__()}】,具体请看日志"
    Log.info(f"{resultContent}错误数据：{failMap}")

    Log.info(f"failNoneMap:{failNoneMap}")
    d2 = changeNoneFailureToDate(failNoneMap)
    resultContent2 = f"校验数据为空，【-6】日k【-7】周k【-8】月k，对比总数【{totalsize}】,校验失败【{failNoneMap.__len__()}】,具体如下【{failNoneMap}】" \
                     f"\n具体修复参数：{d2}"
    Log.info(f"{resultContent2}")

    pushDingDing(resultContent)
    pushDingDing(resultContent2)


    # 后复权数据校验
    # successMap.clear()
    # failMap.clear()
    # pool = ThreadPool(5)
    # pool.map(compareWithkLineAfter, codeList)
    # pool.close()  # 关闭进程池，不再接受新的进程
    # pool.join()  # 主进程阻塞等待子进程的退出
    # totalsize = successMap.__len__() + failMap.__len__()
    # resultContent = f"对比k线[后复权]总数:【{totalsize}】,校验成功:【{successMap.__len__()}】,校验失败【{failMap.__len__()}】,具体请看日志"
    # Log.info(f"{resultContent}错误数据：{failMap}")
    # pushDingDing(resultContent)

if __name__ == '__main__':
    try:
        run()
    except:
        Log.error(traceback.format_exc())

    # checkMongoNotNone('166016',2,2)
